﻿/*
CUDAfy.NET - LGPL 2.1 License
Please consider purchasing a commerical license - it helps development, frees you from LGPL restrictions
and provides you with support.  Thank you!
Copyright (C) 2011 Hybrid DSP Systems
http://www.hybriddsp.com

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Linq;
using System.Text;
//using GASS.CUDA.Types;

namespace Cudafy
{
    /// <summary>
    /// Describes a .NET static that was translated to Cuda constant.
    /// </summary>
    public class KernelConstantInfo : KernelMemberInfo
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="KernelConstantInfo"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="fi">The field information.</param>
        /// <param name="isDummy">if set to <c>true</c> is dummy.</param>
        public KernelConstantInfo(string name, FieldInfo fi, bool isDummy = false)
        {
            Name = name;
            Information = fi;
            IsDummy = isDummy;
        }

        /// <summary>
        /// Gets or sets a handle that can be arbitrarily used to prevent garbage collection.
        /// </summary>
        /// <value>
        /// The handle.
        /// </value>
        public object Handle { get; set; }

        /// <summary>
        /// Gets or sets the cuda pointer.
        /// </summary>
        /// <value>
        /// The cuda pointer.
        /// </value>
        public object CudaPointer { get; set; }
        /// <summary>
        /// Gets the name.
        /// </summary>
        public override string Name { get; internal set; }
        /// <summary>
        /// Gets the information.
        /// </summary>
        public FieldInfo Information { get; internal set; }
        /// <summary>
        /// Gets the type.
        /// </summary>
        public override Type Type { get { return Information.DeclaringType; } }

        internal const string csCUDAFYCONSTANTINFO = "CudafyConstantInfo";

        private const string csFIELDNAME = "FieldName";

        internal string GetDummyDefine()
        {
            if (!IsDummy)
                return string.Empty;
            string ts = string.Format("#define {0} {1}", Name, Information.GetValue(null));
            return ts;
        }

        /// <summary>
        /// Gets the Cuda C declaration string generated by this constant.
        /// </summary>
        /// <returns></returns>
        public string GetDeclaration()
        {
            string fieldName = Information.Name;
            StringBuilder sb = new StringBuilder();
            sb.Append("__constant__ ");

            Type type = Information.FieldType;
            Array array = null;
            int rank = 0;
            if (type.IsArray)
            {
                array = Information.GetValue(null) as Array;
                rank = array.Rank;
                if (rank == 1)
                    type = array.GetValue(0).GetType();
                else if (rank == 2)
                    type = array.GetValue(0, 0).GetType();
                else if (rank == 3)
                    type = array.GetValue(0, 0, 0).GetType();
                else
                    sb.AppendLine("\r\n\t[Constant arrays of more than 3D dimensions are not supported.]");
            }
            if (type.IsArray)
                sb.AppendLine("\r\n\tJagged arrays are not supported.");

            string fieldTypeName = type.Name;

            sb.Append(fieldTypeName);
            sb.Append(" ");
            sb.Append(fieldName);
            //foreach (int dim in dimensions)
            for (int dim = 1; dim <= rank; dim++)
            {
                int len = array.GetUpperBound(dim - 1) + 1;
                sb.Append(string.Format("[{0}]", len));
            }
            sb.AppendLine(";");
            return sb.ToString();
        }

        /// <summary>
        /// Gets the total length.
        /// </summary>
        /// <returns></returns>
        public int GetTotalLength()
        {
            Type type = Information.FieldType;
            Array array = Information.GetValue(null) as Array;
            if (array == null)
                return 1;
            int rank = array.Rank;
            int length = 1;
            for (int dim = 1; dim <= rank; dim++)
            {
                length *= array.GetUpperBound(dim - 1) + 1;
            }
            return length;
        }

        internal override XElement GetXElement()
        {
            XElement xe = new XElement(csCUDAFYCONSTANTINFO);
            xe.SetAttributeValue(csNAME, Name);
            xe.SetAttributeValue(csFIELDNAME, Information.Name);
            xe.SetAttributeValue(csISDUMMY, IsDummy);
            xe.SetAttributeValue(csDUMMYBEHAVIOUR, Behaviour);
            xe.Add(new XElement(csTYPE, this.Type != null ? this.Type.FullName : string.Empty));
            xe.Add(new XElement(csASSEMBLY, this.Type != null ? this.Type.Assembly.FullName : string.Empty));
            xe.Add(new XElement(csASSEMBLYNAME, this.Type != null ? this.Type.Assembly.GetName().Name : string.Empty));
            xe.Add(new XElement(csASSEMBLYPATH, this.Type != null ? this.Type.Assembly.Location : string.Empty));
            xe.Add(new XElement(csCHECKSUM, XmlConvert.ToString(GetAssemblyChecksum())));
            return xe;
        }

        internal static KernelConstantInfo Deserialize(XElement xe, string directory = null)
        {
            string constantName = xe.GetAttributeValue(csNAME);
            string fieldName = xe.GetAttributeValue(csFIELDNAME);
            bool? isDummy = xe.TryGetAttributeBoolValue(csISDUMMY);
            string behaviourStr = xe.TryGetAttributeValue(csDUMMYBEHAVIOUR);  
            string typeName = xe.Element(csTYPE).Value;
            string assemblyFullName = xe.Element(csASSEMBLY).Value;
            string assemblyName = xe.Element(csASSEMBLYNAME).Value;
            string assemblyPath = xe.TryGetElementValue(csASSEMBLYPATH);
            long checksum = XmlConvert.ToInt64(xe.Element(csCHECKSUM).Value);

            FieldInfo fi = null;
            KernelConstantInfo kci = null;

            if (!string.IsNullOrEmpty(typeName) && !string.IsNullOrEmpty(assemblyFullName))
            {
                Assembly assembly = null;
                try
                {
                    assembly = Assembly.Load(assemblyFullName);
                }
                catch (FileNotFoundException)
                {
                    directory = directory != null ? directory : string.Empty;
                    assemblyName = directory + Path.DirectorySeparatorChar + assemblyName;
                    if (File.Exists(assemblyName + ".dll"))
                    {
                        assembly = Assembly.LoadFrom(assemblyName + ".dll");
                    }
                    else if (File.Exists(assemblyName + ".exe"))
                    {
                        assembly = Assembly.LoadFrom(assemblyName + ".exe");
                    }
                    else if (!string.IsNullOrEmpty(assemblyPath))
                    {
                        assembly = Assembly.LoadFrom(assemblyPath);
                    }
                    else
                        throw;
                }
                if (assembly == null)
                    throw new CudafyException(CudafyException.csCOULD_NOT_LOAD_ASSEMBLY_X, assemblyFullName);
                Type type = assembly.GetType(typeName);
                fi = type.GetField(fieldName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
                kci = new KernelConstantInfo(fieldName, fi, isDummy == true ? true : false);
            }
            kci.DeserializedChecksum = checksum;
            return kci;
        }

    }
}
